<!-- WEB 仓库 》 表格 -->

<template>
  <div :class="divClass" :style="divStyle">
    <!-- 表格 -->
    <el-table
      ref="table"
      v-bind="config"
      v-load-more="loadMore"
      @sort-change="changeSort"
      @select="changeSelect"
      @select-all="changeSelect"
      @current-change="changeCurrent"
      @cell-mouse-enter="cellMouseEnter"
      @cell-mouse-leave="cellMouseLeave"
    >
      <slot />

      <template v-if="hasMore && !isEmpty" #append>
        <div :class="`${className}-more`">
          <slot v-if="isFinish" name="finish">{{ finishText }}</slot>
          <slot v-else name="more">{{ moreText }}</slot>
        </div>
      </template>
    </el-table>

    <!-- 分页 -->
    <TablePage v-if="!noPage" v-bind="$props">
      <slot v-if="hasSlotPage" name="page" />
    </TablePage>

    <!-- 空白 -->
    <div v-show="isEmpty" :class="emptyClass">
      <slot name="empty">{{ emptyText }}</slot>
    </div>
  </div>
</template>

<script>
// ============================== 导入依赖 ============================== //

import CORE from '@/CORE'
import mixPath from '../mixPath'
import WebForm from '../WebForm'
import TablePage from './TablePage'

// ============================== 导出组件 ============================== //

export default {
  /**
   * 名称定义 (实际使用名称)
   */
  name: 'WebTable',

  /**
   * 混入列表 (配置选项合并)
   */
  mixins: [mixPath],

  /**
   * 组件导入 (Abc.vue + Abc/index.vue)
   */
  ...CORE.components(require.context('./', true, /vue$/)),

  /**
   * 自定义指令
   */
  directives: {
    /**
     * 加载更多
     */
    'load-more': {
      bind(el, binding) {
        const table = el.querySelector('.el-table__body-wrapper')
        binding.handler = CORE.debounce(function () {
          const { scrollTop, scrollHeight, clientHeight } = table
          if (scrollHeight === scrollTop + clientHeight) binding.value()
        })
        table.addEventListener('scroll', binding.handler)
      },
      unbind(el, binding) {
        const table = el.querySelector('.el-table__body-wrapper')
        table.removeEventListener('scroll', binding.handler)
      },
    },
  },

  /**
   * 属性注册 (抽取以便查阅)
   */
  props: CORE.WEB.WebTable,

  /**
   * 计算属性 (慎用箭头函数)
   */
  computed: {
    /**
     * 尺寸
     */
    size: WebForm.computed.size,

    /**
     * 参数对象
     */
    obj: TablePage.computed.obj,

    /**
     * 样式类
     */
    divClass() {
      const { className, list, state, doubleHead, tripleHead, noTable, noPage } = this
      const data = list || state.list || []
      const lengthStr = ` ${className}-${data.length}`
      const titleStr = doubleHead ? ` ${className}-double` : tripleHead ? ` ${className}-triple` : ''
      const tableStr = noTable ? ` ${className}-noTable` : ''
      const pageStr = noPage ? ` ${className}-noPage` : ''
      return `${className}${lengthStr}${titleStr}${tableStr}${pageStr}`
    },

    /**
     * 样式对象
     */
    divStyle() {
      const { height, minHeight, maxHeight, minFull, maxFull, defaultHeight, fullCutHeight, getSize } = this
      if (height) return { height: getSize(height) }
      if (this.auto) return null
      if (this.full) return { height: '100%' }
      if (minHeight) return { minHeight: getSize(minHeight) }
      if (maxHeight) return { maxHeight: getSize(maxHeight) }
      if (minFull) return { minHeight: '100%' }
      if (maxFull) return { maxHeight: '100%' }
      if (fullCutHeight) return { height: `calc(100% - ${getSize(fullCutHeight)})` }
      if (defaultHeight) return { height: getSize(defaultHeight) }
      return null
    },

    /**
     * 表格配置项
     */
    config() {
      const { list, state, size, sumText, className } = this
      return {
        ...this.elProps,
        data: list || state.list || [],
        rowKey: this.idKey,
        height: this.divStyle ? '100%' : null,
        size,
        showSummary: this.hasSum,
        summaryMethod: this.sumMethod,
        sumText,
        rowClassName: `${className}-row`,
        cellClassName: `${className}-cell`,
      }
    },

    /**
     * 是否有加载更多
     */
    hasMore() {
      const { state: { readyList }, isLongList } = this
      if (CORE.isBoolean(readyList) && !readyList) return false
      if (CORE.isBoolean(isLongList)) return isLongList
      return (this.state.INIT_CONFIG || {}).IS_LONG_LIST || false
    },

    /**
     * 是否空白
     */
    isEmpty() {
      const { $store, pre, config } = this
      if (config.data.length) return false
      if ($store && CORE.isBoolean($store.getters[`${pre}isEmpty`])) return $store.getters[`${pre}isEmpty`]
      return true
    },

    /**
     * 是否完成
     */
    isFinish() {
      const { $store, pre } = this
      if ($store && CORE.isBoolean($store.getters[`${pre}isFinish`])) return $store.getters[`${pre}isFinish`]
      return false
    },

    /**
     * 空白样式类
     */
    emptyClass() {
      const { className, noPage } = this
      return noPage ? `${className}-empty ${className}-empty-full` : `${className}-empty`
    },

    /**
     * 是否有分页插槽
     */
    hasSlotPage() {
      if (!this.$scopedSlots) return false
      return this.$scopedSlots.page
    },
  },

  /**
   * 监听属性 (深度 deep 首次 immediate)
   */
  watch: {
    /**
     * 监听选中列表
     */
    'obj.idList': {
      immediate: true,
      handler() {
        this.resetSelection()
      },
    },

    /**
     * 监听列表数据
     */
    'config.data'() {
      this.resetSelection()
    },
  },

  /**
   * 挂载完成 (先抽取再调用)
   */
  mounted() {
    const refTable = this.$refs.table
    refTable.getData = this.getData
    refTable.export = this.exportXlsx

    // 表格组件保存到 state 中
    if (CORE.isEmpty(this.list) && this.state.refTable !== undefined) this.commit('setRefTable', refTable)
  },

  /**
   * 本地方法 (轻量重要在前)
   */
  methods: {
    /**
     * 切换排序
     */
    changeSort({ order, prop }) {
      const { obj, state, commit, initSort, onSort } = this

      const sort = order ? [prop, order.replace('ending', '')] : initSort || CORE.MAP.PAGE_SORT_LIST
      Object.assign(obj, { page: 1, [CORE.MAP.PAGE_SORT]: sort })

      // 先触发排序事件
      this.$emit('sort', sort)

      // 再执行列表刷新
      if (onSort) onSort()
      else if (state.refTable !== undefined) commit(state.MAIN_START || 'startList')
    },

    /**
     * 切换复选
     */
    changeSelect(rowList) {
      const { obj, idKey } = this
      const idList = rowList.map((row) => row[idKey])

      if (JSON.stringify(idList) === JSON.stringify(obj.idList)) return

      this.$set(obj, 'rowList', rowList)
      this.$set(obj, 'idList', idList)

      this.$emit('select', CORE.clone(idList))
    },

    /**
     * 切换选中行
     */
    changeCurrent(currentRow, oldCurrentRow) {
      this.$emit('current-change', currentRow, oldCurrentRow)
    },

    /**
     * 单元格鼠标进入
     */
    cellMouseEnter(row, column, cell, event) {
      this.$emit('cell-mouse-enter', row, column, cell, event)
    },

    /**
     * 单元格鼠标离开
     */
    cellMouseLeave(row, column, cell, event) {
      this.$emit('cell-mouse-leave', row, column, cell, event)
    },

    /**
     * 重置选中
     */
    async resetSelection() {
      await this.$nextTick()

      const refTable = this.$refs.table
      if (!refTable) return

      const { obj, idKey } = this

      refTable.data.forEach((row) => {
        const current = refTable.selection.some((item) => item[idKey] === row[idKey]) // 当前状态
        const target = (obj.idList || []).includes(row[idKey]) // 目标状态
        if (current !== target) refTable.toggleRowSelection(row, target)
      })
    },

    /**
     * 加载更多
     */
    loadMore() {
      const { hasMore, isFinish, state, commit, onLoadMore } = this
      if (!hasMore || isFinish) return

      if (onLoadMore) onLoadMore()
      else commit(state.MAIN_START || 'startList')
    },

    /**
     * 获取数据
     */
    getData(dataList, baseWidth = 160) {
      // 数据列表
      const list = CORE.isArray(dataList) ? dataList : this.config.data

      // 行组件列表
      const { $children = [] } = this.$refs.table || {}
      const rows = []
      $children.forEach((row) => {
        if (CORE.isFunction(row.getText) && !row.noExport) rows.push(row)
        else {
          row.$children.forEach((row2) => { // 双层表头
            if (CORE.isFunction(row2.getText) && !row2.noExport) rows.push(row2)
          })
        }
      })

      // 二维列表
      const arrayList = list.map((item, i) => {
        return rows.reduce((array, row) => {
          const text = CORE.isFunction(row.exportTrans) ? row.exportTrans(item, row.bind) : row.getText(item)
          const prefix = CORE.isFunction(row.exportPrefix) ? row.exportPrefix(item, row.bind) : row.prefix
          const suffix = CORE.isFunction(row.exportSuffix) ? row.exportSuffix(item, row.bind) : row.suffix
          const middle = row.$options.name === 'RowIndex' ? text || i + 1 : CORE.isNumber(text) ? text : text || ''
          array.push(`${prefix}${middle}${suffix}`)
          return array
        }, [])
      })

      // 有无合计
      if (this.hasSum) {
        const NUM = 1000000 // 放大倍数 (避免精度丢失)
        const sumList = rows.map((row, i) => {
          return list.reduce((sum, item) => {
            const value = Number(row.getValue(item, row.bind))
            return isNaN(value) ? sum : (+sum * NUM + value * NUM) / NUM
          }, '')
        })
        arrayList.push(sumList.map((item, i) => i ? `${item}` : this.sumText))
      }

      // 标题列表
      const titleList = rows.map((row) => row.label)

      // 宽度列表
      const getWidth = ({ width: w }) => w && CORE.isInt(w) ? w : baseWidth // 配置宽度或基础宽度
      const getMinWidth = ({ minWidth: w }) => w && CORE.isInt(w) ? +w - (baseWidth / 2 | 0) : 0 // 去掉一半的基础宽度
      const widthList = rows.map((row) => getWidth(row) + getMinWidth(row))

      // 样式列表
      const styleList = rows.map((row) => {
        if (row.left || row.exportLeft) return { alignment: { horizontal: 'left' } }
        if (row.right || row.exportRight) return { alignment: { horizontal: 'right' } }
        return {}
      })

      // 返回数据
      return { titleList, widthList, arrayList, styleList }
    },

    /**
     * 导出文档
     */
    exportXlsx(fileName, dataList, baseWidth = 160, sheetName = 'Sheet1') {
      // 获取数据
      const data = this.getData(dataList, baseWidth)

      // 导出文档
      CORE.excelExport(fileName, { ...data, sheetName })
    },
  },
}
</script>
